import {fabric} from 'fabric';
import gsApi from '@/api/gsApi';

export default class CanvasManager{
  canvasId = ''
  // 当前地图
  mapName=''
  // 当前的canvas
  canvas=''
  // 当前地图图片
  img=''
  // 拖动地图状态
  moveStatus=false;
  // 编辑地图状态
  editStatus=false;
  // 打点状态
  clickStatus=false
  //显示实时位置的三角形对象
  locatTri;

// 点计画布交互产生的暂存数据
  tempPoints=new Array();


  constructor({canvasId,mapName},opt){
    let defaultOpt={
      width: window.innerWidth*0.5,
      height: window.innerHeight-400,
      isDrawingMode: false,
      selection: false, // 是否启用组选择
      devicePixelRatio: true, // Retina 高清屏 屏幕支持
    }
    Object.assign(defaultOpt, opt);
    this.canvasId=canvasId;
    this.mapName=mapName;

    // 这一步必须要在存在DOM 时候执行，所以一般放在 VUE 的 mounted 事件里
    this.canvas = new fabric.Canvas(canvasId,defaultOpt);

    this.__init();
  }

  __init(){
    // 加载图片
    this.__loadImg(this.mapName);

    // 启动canvas 的鼠标事件
    this.enableMouseFun();
  }

  reloadImg(mapName){
    // 清除画布所有的内容
    this.canvas.remove(...this.canvas.getObjects());
    this.__loadImg(mapName);
  }

  /**
   * 清除画布除了地图的元素
   */
  clearObject(){
    this.canvas.remove(...this.canvas.getObjects());
    this.canvas.add(this.img);
  }

  /**
   * 绘制手画路径
   * @param graphName
   * @param pathName
   * @param dataManager
   */
  drawGraphPath({graphName,pathName,dataManager}){

    //  点、线、路径等完整数据
    const points = dataManager.graphPointsMap.get(graphName);
    const lines = dataManager.graphLinesMap.get(graphName);
    const graph=dataManager.graphList.find(v=>v.name===graphName);
    const path = graph.paths.find(v => v.name === pathName);
    const imgHeight=this.img.height;

    // 一条路径下简略的数据（只包含完整数据的部分，通过名字找到完整数据）
    const lines2=path.lines;
    const points2=path.points;

    // 绘制
    this.drawLine(lines, points, lines2, imgHeight);
    this.drawPoint(points, points2, imgHeight);

  }

  /**
   * 画线
   * @param lines 线段完整数据集
   * @param points  点完整数据集
   * @param lines2  线段简略数据集
   * @param imgHeight 地图高度，转换需要
   */
  drawLine(lines,points,lines2,imgHeight){
    let pathString = '';

    // 根据lines2 寻找 lines,points 对应完整的值
    lines2.forEach((line2,index)=>{
      // 获取完整的值，类似于数据库外键关联
      let currentLine=lines.find(line => line.name === line2.name);
      let beginPoint=points.find(point=>point.name === currentLine.begin);
      let endPoint=points.find(point=>point.name === currentLine.end);

      // 拼写 path 命令
      if (index === 0) {
        pathString += ('M ' + beginPoint.gridPosition.x + ' ' + (imgHeight - beginPoint.gridPosition.y) + ',')
        pathString += (  ' L '+endPoint.gridPosition.x + ' ' + (imgHeight - endPoint.gridPosition.y))
      } else {
        pathString += ( ' L '+endPoint.gridPosition.x + ' ' + (imgHeight - endPoint.gridPosition.y))
      }
    })

    let fabricPath = new fabric.Path(pathString)
    fabricPath.set({ fill: 'rgba(255,255,255,0)', stroke: '#999999', opacity: 1, strokeWidth: 5,selectable: false,hasBorders: false,hasControls: false })
    this.canvas.add(fabricPath)

  }

  /**
   * 画点
   * @param points 点完整数据集
   * @param points2 点简略数据集
   * @param imgHeight 地图高度，转换需要
   */
  drawPoint(points,points2,imgHeight){
      points2.forEach(point2=>{
        let currentPoint = points.find(point => point.name === point2.name);
        const circle=new fabric.Circle({
          radius: 5,
          fill: 'red',
          stroke: '#333333',
          strokeWidth: 0.5,
          left: currentPoint.gridPosition.x,
          //  记得需要转换坐标系的，canvas 上显示坐标系 和 机器人导航的坐标系
          //  canvas的坐标原点在左上角，机器人的坐标原点在左下角
          top: imgHeight-currentPoint.gridPosition.y,
          selectable: false,
          hasBorders: false,
          hasControls: false,
          originX: 'center',
          originY: 'center',
          hoverCursor: 'default'
        })
        this.canvas.add(circle);
      })
  }

  drawCurrentPosition(point){
    let imgHeight = this.img.height;
    const circle=new fabric.Circle({
      radius: 5,
      fill: 'red',
      stroke: '#333333',
      strokeWidth: 0.5,
      angle:point.angle,
      left: point.gridPosition.x,
      //  记得需要转换坐标系的，canvas 上显示坐标系 和 机器人导航的坐标系
      top: imgHeight-point.gridPosition.y,
      selectable: false,
      hasBorders: false,
      hasControls: false,
      originX: 'center',
      originY: 'center',
      hoverCursor: 'default'
    })
    // console.log(circle);
    this.canvas.add(circle);
}
//先用下面的show显示位置，再用这个函数更新位置
moveCurrentPosition(point){
  let imgHeight = this.img.height;
  let left = point.gridPosition.x;
  let top = imgHeight-point.gridPosition.y;
  let angle = -point.angle+90;
  // console.log(left + ' ' + top + ' ' + angle)
  //1.用重新打点的方式显示实时位置
  // this.locatTri.set("left",left);
  // this.locatTri.set("top",top)
  // this.locatTri.set("angle",angle)
  // this.canvas.renderAll()
  //2.用动画的效果处理
  this.locatTri.animate('left',left,{
    onChange: this.canvas.renderAll.bind(this.canvas)
  })
  this.locatTri.animate('top',top,{
    onChange: this.canvas.renderAll.bind(this.canvas)
  })
  this.locatTri.animate('angle',angle,{
    onChange: this.canvas.renderAll.bind(this.canvas)
  })

}

//小车移动时实时显示当前位置点
showCurrentPosition(point){
  this.canvas.remove(this.locatTri)
  let imgHeight = this.img.height;
  this.locatTri=new fabric.Triangle({
    height:10,
    width:6,
    // radius: 4,
    fill: '#FFD700',
    stroke: 'red',
    strokeWidth: 0.5,
    angle:-point.angle+90,    //角度要注意转换！
    left: point.gridPosition.x,
    //  记得需要转换坐标系的，canvas 上显示坐标系 和 机器人导航的坐标系
    top: imgHeight-point.gridPosition.y,
    selectable: false,
    hasBorders: false,
    hasControls: false,
    originX: 'center',
    originY: 'center',
    hoverCursor: 'default'
  })
  // console.log(this.canvas.getObjects())
  this.canvas.add(this.locatTri);
}
  // 这里是异步加载的要注意处理时许的问题
  __loadImg(mapName){
    // 加载某张地图，就设为当前地图
    this.mapName = mapName;
    const imgUrl=gsApi.getMapPngUrl(mapName);
    new fabric.Image.fromURL(imgUrl,oImg=>{
      oImg.id = mapName
      oImg.evented = false
      this.img=oImg
      this.canvas.add(this.img);
      this.zoomTofit()
    })
  }

  /**
   * 调整画布缩放平移
   */
  zoomTofit(){
    // 先还原缩放比例与位置
    this.canvas.setZoom(1)
    this.canvas.absolutePan({ x: 0, y: 0 })
    // 遍历所有对对象，获取最小坐标，最大坐标,即四个角的坐标
    const objects = this.canvas.getObjects()
    let rect, minX, minY, maxX, maxY
    if (objects.length > 0) {
      rect = objects[0].getBoundingRect()
      minX = rect.left
      minY = rect.top
      maxX = rect.left + rect.width
      maxY = rect.top + rect.height
    } else {
      return
    }
    // 计算平移坐标,居中
    const panX = (maxX - minX - this.canvas.width) / 2 + minX
    const panY = (maxY - minY - this.canvas.height) / 2 + minY
    // 开始平移
    this.canvas.absolutePan({ x: panX, y: panY })
    // 计算缩放比例
    const zoom = Math.min(this.canvas.width / (maxX - minX), this.canvas.height / (maxY - minY))
    // 计算缩放中心
    const zoomPoint = new fabric.Point(this.canvas.width / 2, this.canvas.height / 2)
    // 开始缩放
    this.canvas.zoomToPoint(zoomPoint, zoom)
  }

  /**
   * 激活鼠标功能
   */
  enableMouseFun(){
    this.onMouseDown(this.doMouseDown);
    this.onMouseUp();
    this.onMouseMove();
    this.onMouseWheel();
  }


  onMouseDown(doMouseDown=()=>{}){
    this.canvas.on('mouse:down', (options) => {
      // 非打点状态
      if(!this.clickStatus){
        this.moveStatus=true;
      }
      // 编辑状态 同时是 打点状态时候
      else if(this.editStatus&& this.clickStatus){
        // 这里设置成回调函数，方便自由定义
        doMouseDown(options,this)
      }
    })
  }


  /**
   * 打点状态时候要做的操作
   * @param options
   * @param canvasManager
   */
  doMouseDown(options,canvasManager){
    // 1.获取转换过的坐标
    const pointsX = Math.round(canvasManager.canvas.getPointer(options.e).x)
    const pointsY = Math.round(canvasManager.canvas.getPointer(options.e).y)

    // 2.记录数据
    const pointName=canvasManager.tempPoints.length.toString();
    const currentPoint= {
      name: pointName,
      x: pointsX,
      y: pointsY
    }
    canvasManager.tempPoints.push(currentPoint);

    // 3.视图渲染
    // 3.1 绘制线段
    if(canvasManager.tempPoints.length>1){
      const length=canvasManager.tempPoints.length;
      const lastPoint=canvasManager.tempPoints[length-2]
      const line=canvasManager.getSvgPathElement(lastPoint,currentPoint);
      canvasManager.canvas.add(line);
    }

    // 3.2 绘制点
    const circle=canvasManager.getCircleElement({
      x:pointsX,
      y:pointsY,
      radius:5,
      color:'red',
    })
    canvasManager.canvas.add(circle);

  }

  onMouseMove(doMouseMove=()=>{}){
    this.canvas.on('mouse:move', (options) => {
      if(!this.clickStatus && this.moveStatus){
        const delta = new fabric.Point(options.e.movementX, options.e.movementY);
        this.canvas.relativePan(delta)
      }
      else if(this.editStatus && this.clickStatus){
        // 这里设置成回调函数，方便自定义扩展
        doMouseMove(options,this)
      }
    })
  }

  onMouseUp(doMouseUp=()=>{}){
    this.canvas.on('mouse:up',options=>{
      if(!this.clickStatus){
        this.moveStatus=false;
      }
      else{
        // 这里设置成回调函数，方便自定义扩展
        doMouseUp(options,this)
      }
    })
  }

  onMouseWheel(){
    // 滚动缩放画布大小
    this.canvas.on('mouse:wheel', (options) => {
      let zoom = (options.e.deltaY > 0 ? -0.1 : 0.1) + this.canvas.getZoom()
      // 最小为原来的1/10
      zoom = Math.max(0.1, zoom)
      // 最大是原来的3倍
      zoom = Math.min(5, zoom)
      // 在中心缩放
      const zoomPoint = new fabric.Point(this.canvas.width / 2, this.canvas.height / 2)
      this.canvas.zoomToPoint(zoomPoint, zoom)
    })
  }

  /**
   * 返回一个fabric.Circle
   * @param x
   * @param y
   * @param radius
   * @param color
   */
  getCircleElement({x,y,radius,color}) {
    return new fabric.Circle({
      radius: radius,
      fill: color,
      stroke: '#333333',
      strokeWidth: 0.5,
      left: x,
      top: y,
      selectable: false,
      hasBorders: false,
      hasControls: false,
      originX: 'center',
      originY: 'center',
      hoverCursor: 'default'
    })
  }

  /**
   * 返回一个fabric.Path
   * @param beginPoint
   * @param endPoint
   */
    getSvgPathElement(beginPoint,endPoint){
      // todo
      let pathString=`M ${beginPoint.x} ${beginPoint.y} L ${endPoint.x} ${endPoint.y}` ;
      let fabricPath = new fabric.Path(pathString)
      fabricPath.set({
        fill: 'rgba(255,255,255,0)',
        stroke: '#999999',
        opacity: 1,
        strokeWidth: 5,
        selectable: false,
        hasBorders: false,
        hasControls: false,

      });
      return fabricPath;
    }


  /**
   * 根据画布交互暂存的数据，产生将要提交的数据
   * 注意：这里简单处理画布的交互过程，默认将画布产生的数据视为合理的数据
   * 但实际应用中，要验证手画的线段时候安全：点和点之间能否生成线段。线段和线段能否生成路径等等
   */
  generateData(){
    const length=this.tempPoints.length;
    let pointBeans=[];
    let lineBeans=[];
    if(length<2){
      console.log('不能小于2个点')
      return null;
    }
    const imgHeight=this.img.height;
    this.tempPoints.forEach((point,index)=>{
      // 坐标系之间需要做转换
      const pointBean=gsApi.createPointBean({
            name:point.name,
            x:point.x,
            y:imgHeight-point.y,
      });
      pointBeans.push(pointBean);
      // 第一个点就不需要生成线段
      if(index===0){
        return
      }
      const beginPoint=pointBeans[index-1];
      const endPoint=pointBean;
      const lineName=beginPoint.name+"_"+endPoint.name;
      const lineBean=gsApi.createLineBean(lineName,beginPoint.name,endPoint.name,0);
      lineBeans.push(lineBean);
    })

    const pathBean=gsApi.createPathBean({
      // 默认给了个名字，这里只演示单条路径
      name:'path0',
      lines:lineBeans,
      points:pointBeans,
    })

    // 随机生成名字，名字不能重复，这里是demo就简单处理
    const graphName='auto'+(Math.floor(Math.random()*(9999-1000))+1000);
    const graphBean=gsApi.createGraphBean({
      mapName:this.mapName,
      name:graphName,
      paths:[pathBean],
      lines:lineBeans,
      points:pointBeans,
    })

    return graphBean;
  }




}
